package com.google.gson.internal;

import cn.hutool.core.util.TypeUtil;

import java.io.Serializable;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.NoSuchElementException;

import static java.util.Objects.requireNonNull;

public class $Gson$Types {

    /**
     * Returns the component type of this array type.
     * @throws ClassCastException if this type is not an array.
     */
    public static Type getArrayComponentType(Type array) {
        return array instanceof GenericArrayType
                ? ((GenericArrayType) array).getGenericComponentType()
                : ((Class<?>) array).getComponentType();
    }

    public static Class<?> getRawType(Type type) {
        return TypeUtil.getClass(type);
    }


    /**
     * Returns a type that is functionally equal but not necessarily equal
     * according to {@link Object#equals(Object) Object.equals()}. The returned
     * type is {@link java.io.Serializable}.
     */
    public static Type canonicalize(Type type) {
        if (type instanceof Class) {
            Class<?> c = (Class<?>) type;
            return c;

        } else if (type instanceof ParameterizedType) {
            ParameterizedType p = (ParameterizedType) type;
            return new ParameterizedTypeImpl(p.getOwnerType(), p.getRawType(), p.getActualTypeArguments());

        } else {
            // type is either serializable as-is or unsupported
            return type;
        }
    }

    public static Type resolve(Type context, Type toResolve) {
        if (toResolve instanceof TypeVariable) {
            TypeVariable<?> typeVariable = (TypeVariable<?>) toResolve;

            toResolve = resolveTypeVariable(context, typeVariable);
        }

        return toResolve;
    }

    private static Type resolveTypeVariable(Type context, TypeVariable<?> unknown) {
        // 獲取包含類型變量的類
        Class<?> declaredByRaw = declaringClassOf(unknown);

        // we can't reduce this further
        if (declaredByRaw == null) {
            return unknown;
        }

        Type declaredBy = context;
        if (declaredBy instanceof ParameterizedType) {
            int index = indexOf(declaredByRaw.getTypeParameters(), unknown);
            return ((ParameterizedType) declaredBy).getActualTypeArguments()[index];
        }

        return unknown;
    }

    private static int indexOf(Object[] array, Object toFind) {
        for (int i = 0, length = array.length; i < length; i++) {
            if (toFind.equals(array[i])) {
                return i;
            }
        }
        throw new NoSuchElementException();
    }


    /**
     * Returns the declaring class of {@code typeVariable}, or {@code null} if it was not declared by
     * a class.
     */
    private static Class<?> declaringClassOf(TypeVariable<?> typeVariable) {
        GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration();
        return genericDeclaration instanceof Class
                ? (Class<?>) genericDeclaration
                : null;
    }

    private static final class ParameterizedTypeImpl implements ParameterizedType, Serializable {

        private final Type ownerType;
        private final Type rawType;
        private final Type[] typeArguments;

        public ParameterizedTypeImpl(Type ownerType, Type rawType, Type... typeArguments) {
            requireNonNull(rawType);

            this.ownerType = ownerType;
            this.rawType = canonicalize(rawType);
            this.typeArguments = typeArguments.clone();
        }

        @Override public Type[] getActualTypeArguments() {
            return typeArguments.clone();
        }

        @Override public Type getRawType() {
            return rawType;
        }

        @Override public Type getOwnerType() {
            return ownerType;
        }
    }
}
